From 6c86916e81fa18394d9b57b4af44f9948e100e96 Mon Sep 17 00:00:00 2001
From: "Miouyouyou (Myy)" <myy@miouyouyou.fr>
Date: Sat, 29 Sep 2018 03:02:10 +0200
Subject: [PATCH 6/6] drm: dw_hdmi-rockchip: better clock selection logic and
 dts-based rate list

This patch is taken from Urja Rannikko ( @urjaman ) patchset here :
https://github.com/urjaman/arch-c201/blob/master/linux-c201/0020-RK3288-HDMI-clock-hacks-combined.patch
https://www.spinics.net/lists/arm-kernel/msg673156.html

The original description was :
    This contains traces of the following commits from the ChromeOS 3.14
    tree, which improve RF/EMI performance and detach the clock selection
    logic from the HDMI PHY configurations, plus support for configuring
    the allowed clock rates via device tree as they are dependent on
    PLL configuration and maybe even the PCB layout and other hardware things,
    eg. interference to wifi or such (EMI).

    Rates that were allowed previous to this patch are added as the fallback
    list if no dts configuration exists.

    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: Adjust rockchip_mpll_cfg for 146.25
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: expand the informal mpll config
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: add slop to more tables
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: redo rockchip hdmi to allow slop
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: Use auto-generated tables
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: Fixup the clock to be what we expect
    CHROMIUM: drm/rockchip: hdmi: adjust cklvl & txlvl for RF/EMI
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: Set cur_ctr to 0 always
    CHROMIUM: drm: rockchip/dw_hdmi-rockchip: Decrease slop

https://www.spinics.net/lists/arm-kernel/msg673163.html

This is the patch that takes into account the new property
"rockchip,hdmi-rates-hz" that allows the definition of the HDMI
frequencies in the DTS file.
This also change a lot of HDMI frequencies definition, so that
*will* require some extensive testing.

Still, if it works fine, this should make tinkering the HDMI
frequencies easier, in case you have a very special HDMI screen.

Signed-off-by: Miouyouyou (Myy) <myy@miouyouyou.fr>
---
 drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 269 ++++++++++++++++++----------
 1 file changed, 175 insertions(+), 94 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 11309a2a4..740b0aeea 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -49,122 +49,141 @@ struct rockchip_hdmi {
 	struct clk *vpll_clk;
 	struct clk *grf_clk;
 	struct dw_hdmi *hdmi;
+	u32* rates;
+	u32 rates_cnt;
 };
 
+#define CLK_SLOP(clk)		((clk) / 1000)
+#define CLK_PLUS_SLOP(clk)	((clk) + CLK_SLOP(clk))
+
 #define to_rockchip_hdmi(x)	container_of(x, struct rockchip_hdmi, x)
 
+/* These were the rates allowed by the driver before rates list in device tree,
+ * so keep them around as a fallback */
+static const u32 dw_hdmi_fallback_rates[] = {
+	27000000,
+	36000000,
+	40000000,
+	54000000,
+	65000000,
+	66000000,
+	74250000,
+	83500000,
+	106500000,
+	108000000,
+	146250000,
+	148500000
+};
+
 static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
 	{
-		27000000, {
-			{ 0x00b3, 0x0000},
-			{ 0x2153, 0x0000},
-			{ 0x40f3, 0x0000}
+		30666000, {
+			{ 0x00b3, 0x0000 },
+			{ 0x2153, 0x0000 },
+			{ 0x40f3, 0x0000 },
+		},
+	},  {
+		36800000, {
+			{ 0x00b3, 0x0000 },
+			{ 0x2153, 0x0000 },
+			{ 0x40a2, 0x0001 },
 		},
-	}, {
-		36000000, {
-			{ 0x00b3, 0x0000},
-			{ 0x2153, 0x0000},
-			{ 0x40f3, 0x0000}
+	},  {
+		46000000, {
+			{ 0x00b3, 0x0000 },
+			{ 0x2142, 0x0001 },
+			{ 0x40a2, 0x0001 },
 		},
-	}, {
-		40000000, {
-			{ 0x00b3, 0x0000},
-			{ 0x2153, 0x0000},
-			{ 0x40f3, 0x0000}
+	},  {
+		61333000, {
+			{ 0x0072, 0x0001 },
+			{ 0x2142, 0x0001 },
+			{ 0x40a2, 0x0001 },
 		},
-	}, {
-		54000000, {
-			{ 0x0072, 0x0001},
-			{ 0x2142, 0x0001},
-			{ 0x40a2, 0x0001},
+	},  {
+		73600000, {
+			{ 0x0072, 0x0001 },
+			{ 0x2142, 0x0001 },
+			{ 0x4061, 0x0002 },
 		},
-	}, {
-		65000000, {
-			{ 0x0072, 0x0001},
-			{ 0x2142, 0x0001},
-			{ 0x40a2, 0x0001},
+	},  {
+		92000000, {
+			{ 0x0072, 0x0001 },
+			{ 0x2145, 0x0002 },
+			{ 0x4061, 0x0002 },
 		},
-	}, {
-		66000000, {
-			{ 0x013e, 0x0003},
-			{ 0x217e, 0x0002},
-			{ 0x4061, 0x0002}
+	},  {
+		122666000, {
+			{ 0x0051, 0x0002 },
+			{ 0x2145, 0x0002 },
+			{ 0x4061, 0x0002 },
 		},
-	}, {
-		74250000, {
-			{ 0x0072, 0x0001},
-			{ 0x2145, 0x0002},
-			{ 0x4061, 0x0002}
+	},  {
+		147200000, {
+			{ 0x0051, 0x0002 },
+			{ 0x2145, 0x0002 },
+			{ 0x4064, 0x0003 },
 		},
-	}, {
-		83500000, {
-			{ 0x0072, 0x0001},
+	},  {
+		184000000, {
+			{ 0x0051, 0x0002 },
+			{ 0x214c, 0x0003 },
+			{ 0x4064, 0x0003 },
 		},
-	}, {
-		108000000, {
-			{ 0x0051, 0x0002},
-			{ 0x2145, 0x0002},
-			{ 0x4061, 0x0002}
+	},  {
+		226666000, {
+			{ 0x0040, 0x0003 },
+			{ 0x214c, 0x0003 },
+			{ 0x4064, 0x0003 },
 		},
-	}, {
-		106500000, {
-			{ 0x0051, 0x0002},
-			{ 0x2145, 0x0002},
-			{ 0x4061, 0x0002}
+	},  {
+		272000000, {
+			{ 0x0040, 0x0003 },
+			{ 0x214c, 0x0003 },
+			{ 0x5a64, 0x0003 },
 		},
-	}, {
-		146250000, {
-			{ 0x0051, 0x0002},
-			{ 0x2145, 0x0002},
-			{ 0x4061, 0x0002}
+	},  {
+		340000000, {
+			{ 0x0040, 0x0003 },
+			{ 0x3b4c, 0x0003 },
+			{ 0x5a64, 0x0003 },
 		},
-	}, {
-		148500000, {
-			{ 0x0051, 0x0003},
-			{ 0x214c, 0x0003},
-			{ 0x4064, 0x0003}
+	},  {
+		600000000, {
+			{ 0x1a40, 0x0003 },
+			{ 0x3b4c, 0x0003 },
+			{ 0x5a64, 0x0003 },
 		},
-	}, {
+	},  {
 		~0UL, {
-			{ 0x00a0, 0x000a },
-			{ 0x2001, 0x000f },
-			{ 0x4002, 0x000f },
+			{ 0x0000, 0x0000 },
+			{ 0x0000, 0x0000 },
+			{ 0x0000, 0x0000 },
 		},
 	}
 };
 
 static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
-	/*      pixelclk    bpp8    bpp10   bpp12 */
+	/*      pixelclk     bpp8    bpp10   bpp12 */
 	{
-		40000000,  { 0x0018, 0x0018, 0x0018 },
-	}, {
-		65000000,  { 0x0028, 0x0028, 0x0028 },
-	}, {
-		66000000,  { 0x0038, 0x0038, 0x0038 },
-	}, {
-		74250000,  { 0x0028, 0x0038, 0x0038 },
-	}, {
-		83500000,  { 0x0028, 0x0038, 0x0038 },
-	}, {
-		146250000, { 0x0038, 0x0038, 0x0038 },
-	}, {
-		148500000, { 0x0000, 0x0038, 0x0038 },
-	}, {
-		~0UL,      { 0x0000, 0x0000, 0x0000},
-	}
+		600000000, { 0x0000, 0x0000, 0x0000 },
+	},  {
+		~0UL,      { 0x0000, 0x0000, 0x0000 },
+	},
 };
 
 static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
 	/*pixelclk   symbol   term   vlev*/
-	{ 74250000,  0x8009, 0x0004, 0x0272},
-	{ 148500000, 0x802b, 0x0004, 0x028d},
-	{ 297000000, 0x8039, 0x0005, 0x028d},
-	{ ~0UL,	     0x0000, 0x0000, 0x0000}
+	{ CLK_PLUS_SLOP(74250000),  0x8009, 0x0004, 0x0272},
+	{ CLK_PLUS_SLOP(165000000), 0x802b, 0x0004, 0x0209},
+	{ CLK_PLUS_SLOP(297000000), 0x8039, 0x0005, 0x028d},
+	{ ~0UL,	                    0x0000, 0x0000, 0x0000}
 };
 
 static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
 {
 	struct device_node *np = hdmi->dev->of_node;
+	int rates_cnt;
 
 	hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
 	if (IS_ERR(hdmi->regmap)) {
@@ -192,26 +211,55 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
 		return PTR_ERR(hdmi->grf_clk);
 	}
 
+	if ((rates_cnt = of_property_count_u32_elems(np, "rockchip,hdmi-rates-hz")) > 0) {
+		int rv;
+		u32 *rates = devm_kmalloc_array(hdmi->dev, rates_cnt, sizeof(u32), GFP_KERNEL);
+		if (!rates)
+			return -ENOMEM;
+		rv = of_property_read_u32_array(np, "rockchip,hdmi-rates-hz", rates, rates_cnt);
+		if (rv)
+			return rv;
+		hdmi->rates = rates;
+		hdmi->rates_cnt = rates_cnt;
+	} else {
+		rates_cnt = ARRAY_SIZE(dw_hdmi_fallback_rates);
+		hdmi->rates = devm_kmalloc_array(hdmi->dev, rates_cnt, sizeof(u32), GFP_KERNEL);
+		if (!hdmi->rates)
+			return -ENOMEM;
+		memcpy(hdmi->rates, dw_hdmi_fallback_rates, rates_cnt * sizeof(u32));
+		hdmi->rates_cnt = rates_cnt;
+	}
+
+
 	return 0;
 }
 
 static enum drm_mode_status
-dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
+dw_hdmi_rockchip_encoder_mode_valid(struct drm_encoder *encoder,
 			    const struct drm_display_mode *mode)
 {
-	const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
+	struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
 	int pclk = mode->clock * 1000;
-	bool valid = false;
+	int num_rates = hdmi->rates_cnt;
 	int i;
 
-	for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
-		if (pclk == mpll_cfg[i].mpixelclock) {
-			valid = true;
-			break;
-		}
+	/*
+	 * Pixel clocks we support are always < 2GHz and so fit in an
+	 * int.  We should make sure source rate does too so we don't get
+	 * overflow when we multiply by 1000.
+	 */
+	if (mode->clock > INT_MAX / 1000)
+		return MODE_BAD;
+
+	for (i = 0; i < num_rates; i++) {
+		int slop = CLK_SLOP(pclk);
+
+		if ((pclk >= hdmi->rates[i] - slop) &&
+		    (pclk <= hdmi->rates[i] + slop))
+			return MODE_OK;
 	}
 
-	return (valid) ? MODE_OK : MODE_BAD;
+	return MODE_BAD;
 }
 
 static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
@@ -227,7 +275,39 @@ dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
 				    const struct drm_display_mode *mode,
 				    struct drm_display_mode *adj_mode)
 {
-	return true;
+	struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+	int pclk = adj_mode->clock * 1000;
+	int best_diff = INT_MAX;
+	int best_clock = 0;
+	int slop;
+	int i;
+
+	/* Pick the best clock */
+	for (i = 0; i < hdmi->rates_cnt; i++) {
+		int diff = hdmi->rates[i] - pclk;
+
+		if (diff < 0)
+			diff = -diff;
+		if (diff < best_diff) {
+			best_diff = diff;
+			best_clock = hdmi->rates[i];
+
+			/* Bail early if we're exact */
+			if (best_diff == 0)
+				return true;
+		}
+	}
+
+	/* Double check that it's OK */
+	slop = CLK_SLOP(pclk);
+	if ((pclk >= best_clock - slop) && (pclk <= best_clock + slop)) {
+		adj_mode->clock = DIV_ROUND_UP(best_clock, 1000);
+		return true;
+	}
+
+	/* Shoudn't be here; we should have said rate wasn't valid */
+	dev_warn(hdmi->dev, "tried to set invalid rate %d\n", adj_mode->clock);
+	return false;
 }
 
 static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
@@ -280,6 +360,7 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
 }
 
 static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
+	.mode_valid = dw_hdmi_rockchip_encoder_mode_valid,
 	.mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
 	.mode_set   = dw_hdmi_rockchip_encoder_mode_set,
 	.enable     = dw_hdmi_rockchip_encoder_enable,
@@ -294,7 +375,6 @@ static struct rockchip_hdmi_chip_data rk3288_chip_data = {
 };
 
 static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = {
-	.mode_valid = dw_hdmi_rockchip_mode_valid,
 	.mpll_cfg   = rockchip_mpll_cfg,
 	.cur_ctr    = rockchip_cur_ctr,
 	.phy_config = rockchip_phy_config,
@@ -308,7 +388,6 @@ static struct rockchip_hdmi_chip_data rk3399_chip_data = {
 };
 
 static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
-	.mode_valid = dw_hdmi_rockchip_mode_valid,
 	.mpll_cfg   = rockchip_mpll_cfg,
 	.cur_ctr    = rockchip_cur_ctr,
 	.phy_config = rockchip_phy_config,
@@ -387,6 +466,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
 	 */
 	if (IS_ERR(hdmi->hdmi)) {
 		ret = PTR_ERR(hdmi->hdmi);
+		devm_kfree(hdmi->dev, hdmi->rates);
 		drm_encoder_cleanup(encoder);
 		clk_disable_unprepare(hdmi->vpll_clk);
 	}
@@ -399,6 +479,7 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
 {
 	struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
 
+	devm_kfree(hdmi->dev, hdmi->rates);
 	dw_hdmi_unbind(hdmi->hdmi);
 	clk_disable_unprepare(hdmi->vpll_clk);
 }
-- 
2.16.4

